﻿using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace IssueIT.Core.Util
{
	public interface IObjectConverterFamily
	{
		bool Matches(Type type, IObjectConverter converter);
		Func<string, object> CreateConverter(Type type, Cache<Type, Func<string, object>> converters);
	}

	public class ArrayConverterFamily : IObjectConverterFamily
	{
		public bool Matches(Type type, IObjectConverter converter)
		{
			if( type.IsArray && converter.CanBeParsed(type.GetElementType()) )
			{
				return true;
			}

			return IsGenericEnumerable(type) && converter.CanBeParsed(type.GetGenericArguments()[0]);
		}

		public Func<string, object> CreateConverter(Type type, Cache<Type, Func<string, object>> converters)
		{
			var elementType = IsGenericEnumerable(type) ? type.GetGenericArguments()[0] : type.GetElementType();

			var converter = converters[elementType];

			return stringValue =>
			{
				if( stringValue.ToUpper()==ObjectConverter.EmptyString )
				{
					return Array.CreateInstance(elementType, 0);
				}

				var strings = stringValue.Split(',');
				var array = Array.CreateInstance(elementType, strings.Length);

				for(var i = 0; i<strings.Length; i++)
				{
					var value = converter(strings[i]);
					array.SetValue(value, i);
				}

				return array;
			};
		}

		public static bool IsGenericEnumerable(Type type)
		{
			var genericArgs = type.GetGenericArguments();
			return genericArgs.Length==1 && type.IsAssignableFrom(typeof(IEnumerable<>).MakeGenericType(genericArgs));
		}
	}

	public class EnumConverterFamily : IObjectConverterFamily
	{
		public bool Matches(Type type, IObjectConverter converter)
		{
			return type.IsEnum;
		}

		public Func<string, object> CreateConverter(Type type, Cache<Type, Func<string, object>> converters)
		{
			return s => Enum.Parse(type, s);
		}
	}

	public class NullableConverterFamily : IObjectConverterFamily
	{
		public bool Matches(Type type, IObjectConverter converter)
		{
			return type.IsGenericType && !type.IsGenericTypeDefinition && type.GetGenericTypeDefinition()==typeof(Nullable<>);
		}

		public Func<string, object> CreateConverter(Type type, Cache<Type, Func<string, object>> converters)
		{
			var innerType = type;
			if( type.IsGenericType && !type.IsGenericTypeDefinition )
			{
				innerType = type.GetGenericArguments()[0];
			}

			var inner = converters[innerType];

			return stringValue =>
			{
				if( stringValue==ObjectConverter.NullString || stringValue==null )
				{
					return null;
				}
				return inner(stringValue);
			};
		}
	}

	public class TypeDescriptorFamily : IObjectConverterFamily
	{
		public bool Matches(Type type, IObjectConverter converter)
		{
			var typeDescriptor = TypeDescriptor.GetConverter(type);
			return typeDescriptor!=null && typeDescriptor.CanConvertFrom(typeof(string));
		}

		public Func<string, object> CreateConverter(Type type, Cache<Type, Func<string, object>> converters)
		{
			var converter = TypeDescriptor.GetConverter(type);
			if( converter==null )
			{
				throw new InvalidOperationException(string.Format("No TypeDescriptor available for {0}", type.FullName));
			}
			return s => converter.ConvertFromString(s);
		}
	}
}